摘要
Day 18 已經把驗證流程放進 Docker 與 CI quality gate。Day 19 把既有規則測試、分層驗證測試、情境測試與版本比較測試整理成可閱讀的覆蓋證據,並整理首頁結果。
前面幾天已經可以回答:
這份 Bundle 在 JSON、FHIR R4、TW Core 與交換契約規則下能不能通過?
但對資料交換來說,只知道結果還不夠。
還要能回答另一個問題:
這個 Passed / Blocked 判斷,有沒有測試證據支撐?
如果驗證結果只是一個畫面狀態,讀者很難知道:
PASS、FAIL、NOT_APPLICABLE、NOT_EVALUATED 有沒有被正確區分。所以 Day 19 的重點是:
把散在測試裡的行為,整理成可以檢查的覆蓋證據。
把首頁整理成一份可以被使用者閱讀的 Quality Test Report。
這不是新驗證邏輯。
它是把目前 MVP 的證據鏈整理清楚。
今天新增或修改的範圍有:
docs/journal/20260820.md
src/main/resources/templates/index.html
src/main/java/com/twlab/qualitygate/validation/*
src/main/java/com/twlab/qualitygate/web/ParseController.java
src/test/java/com/twlab/qualitygate/web/ParseControllerTests.java
src/test/java/com/twlab/qualitygate/validation/*Tests.java
Day 19 做三件事:
整理六條交換契約規則覆蓋矩陣。
整理分層驗證與契約版本情境覆蓋表。
把首頁統一成 English-first Quality Test Report。
Day19 更新後確認:
./mvnw test
Tests run: 60, Failures: 0, Errors: 0, Skipped: 0
測試數通過很重要,但還不夠。
因為 60 個測試可能集中在某幾條規則,也可能只測 happy path。
對資料品質閘門來說,更重要的是每條規則的狀態語意是否被固定下來。
本專案目前的交換契約規則結果有四種:
| Outcome | 意義 |
|---|---|
PASS |
規則已執行,資料符合交換契約條件 |
FAIL |
規則已執行,資料違反交換契約條件 |
NOT_APPLICABLE |
規則不適用目前 Bundle 內容 |
NOT_EVALUATED |
規則需要的判斷超出 MVP 可評估邊界 |
這四種狀態不能混用。
例如外部 HTTP reference 目前沒有外部 FHIR Server 查詢能力。
它不能被寫成 PASS,因為系統其實沒有查到。
它也不一定應該直接寫成 FAIL,因為資料可能存在於外部 server。
所以 reference 類規則需要 NOT_EVALUATED。
但 LOINC / UCUM 契約允許集合規則不同。
它們只檢查 Bundle 內 Observation 的 coding 或 quantity 欄位,不需要外部查詢。
所以目前沒有 NOT_EVALUATED 案例,是設計邊界,不是測試缺漏。
這張矩陣只放六條交換契約規則。
TW Core validation 和契約版本比較不放在這裡,避免把不同層級混在同一張表。
| 規則 | 規則目的 | PASS 證據 |
FAIL 證據 |
NOT_APPLICABLE 證據 |
NOT_EVALUATED 證據 |
測試類別 |
|---|---|---|---|---|---|---|
LAB-REF-001 |
Observation.subject 必須指向 Bundle 內 Patient |
valid-internal-reference.json / passesWhenObservationSubjectPointsToBundlePatientById |
missing-internal-reference.json / failsWhenObservationSubjectPatientIsMissingFromBundle |
unsupported-resource-in-bundle.json / isNotApplicableWhenBundleHasNoObservation |
external-http-reference.json / doesNotEvaluateExternalHttpReference |
LabRef001ObservationSubjectRuleTests |
LAB-REF-002 |
DiagnosticReport.result 必須指向 Bundle 內 Observation |
valid-internal-reference.json、valid-report-result-full-url-reference.json |
missing-report-result-reference.json、missing-report-result-field.json |
unsupported-resource-in-bundle.json / isNotApplicableWhenBundleHasNoDiagnosticReport |
external-report-result-reference.json / doesNotEvaluateExternalHttpReference |
LabRef002DiagnosticReportResultRuleTests |
LAB-REF-003 |
DiagnosticReport.subject 與 referenced Observation.subject 必須是同一 Patient |
valid-internal-reference.json、report-subject-full-url-observation-subject-id.json |
mismatched-report-observation-patient.json、missing-report-result-reference.json、missing-report-result-field.json |
unsupported-resource-in-bundle.json / isNotApplicableWhenBundleHasNoDiagnosticReport |
external-report-result-reference.json、external-observation-subject-reference.json |
LabRef003ReportObservationPatientRuleTests |
LAB-CODE-001 |
Observation.code 必須包含契約允許的 LOINC coding |
valid-loinc-code.json / passesWhenObservationHasAllowedLoincCode |
loinc-code-not-allowed.json、observation-code-without-coding.json、observation-coding-without-code.json |
unsupported-resource-in-bundle.json / isNotApplicableWhenBundleHasNoObservation |
不適用:此規則只檢查 Bundle 內 coding,不查外部 terminology server | LabCode001ObservationLoincRuleTests |
LAB-UNIT-001 |
Quantity 檢驗值必須有可讀 valueQuantity.unit |
valid-minimal-lab-bundle.json / passesWhenQuantityHasReadableUnit |
observation-quantity-without-unit.json / failsWhenQuantityHasNoUnit |
observation-value-string.json、unsupported-resource-in-bundle.json |
不適用:此規則只檢查 Bundle 內 Quantity.unit | LabUnit001ObservationQuantityUnitRuleTests |
LAB-UNIT-002 |
Quantity 檢驗值必須有允許的 UCUM system/code |
valid-ucum-code.json / passesWhenQuantityHasAllowedUcumSystemAndCode |
observation-quantity-wrong-ucum-system.json、observation-quantity-ucum-code-not-allowed.json、observation-quantity-without-ucum-code.json |
observation-value-string.json、unsupported-resource-in-bundle.json |
不適用:此規則只做契約允許集合檢查,不做完整 UCUM terminology validation | LabUnit002ObservationUcumCodeRuleTests |
這張表暴露出三件事。
第一,六條規則都有獨立 FAIL 證據。
這符合目前 MVP 的最低要求:
每條規則至少有獨立 Fail case。
第二,NOT_EVALUATED 目前集中在 Reference 類規則。
因為 LAB-REF-001、LAB-REF-002、LAB-REF-003 都可能遇到外部 HTTP reference。
目前 MVP 沒有外部 FHIR Server 查詢能力,所以只能標示尚未評估。
第三,LAB-REF-001 原先沒有獨立 NOT_APPLICABLE 測試。
因此補上沒有 Observation 的 unsupported-resource-in-bundle.json,確認此規則不會把「不適用」誤寫成 PASS 或 FAIL。
首頁的 Quality Gate 不只包含六條交換契約規則。
它還包含 JSON parse、FHIR R4 parse、Resource Type Gate、FHIR R4 validation 與 TW Core validation。
所以這些層級另外整理成分層驗證覆蓋表:
| 驗證層 | 主要目的 | 已覆蓋狀態 | 代表測試 / fixture | 備註 |
|---|---|---|---|---|
| JSON parse | 確認輸入是否為合法 JSON | PASSED, FAILED |
BundleParseServiceTests.reportsInvalidJsonWithoutThrowing |
JSON 失敗時後續 FHIR / 契約層不執行 |
| FHIR R4 parse | 確認 JSON 可被 HAPI FHIR 解析為 R4 Resource | PASSED, FAILED |
BundleParseServiceTests 的非法輸入與合法 Bundle 測試 |
這層是 FHIR parser,不等同 Profile validation |
| Resource Type Gate | 確認入口只處理 Bundle.type = collection |
PASSED, FAILED |
rendersResourceInventoryForSupportedAndUnsupportedEntries, non-Bundle / missing type 測試 |
不支援 Resource 不冒充已驗證 |
| FHIR R4 validation | 執行 HAPI FHIR R4 validation 並保留 OperationOutcome | PASSED, FAILED, NOT_EVALUATED |
rendersFhirValidationIssue, invalid JSON 測試 |
warning 顯示但不一定阻擋 |
| TW Core validation | 執行 TW Core 或安全降級 | PASSED, FAILED, NOT_EVALUATED |
BundleParseServiceTests 的 stub passed / failed / fallback 測試 |
無法穩定驗證時顯示 NOT_EVALUATED,不冒充 Profile 通過 |
| Exchange contract rules | 執行六條交換契約規則 | PASS, FAIL, NOT_APPLICABLE, NOT_EVALUATED |
六條規則測試類別 | 詳細證據在六條交換契約規則覆蓋矩陣 |
| Quality Gate | 彙總前面各層與契約規則結果 | PASSED, PASS_WITH_WARNINGS, BLOCKED |
BundleParseServiceTests |
TW Core 明確失敗或契約規則失敗會阻擋 |
這張表的目的,是把「首頁顯示的每一層」和「測試裡保護的行為」對起來。
單條規則測試確認規則本身。
情境測試則確認多條規則一起跑時,Quality Gate 和契約版本比較仍然符合預期。
目前 ContractScenarioCaseTests 覆蓋 4 組代表案例:
| 情境 | Fixture | v1.0 Gate | v1.1 Gate | v1.0 failed rules | v1.1 failed rules |
|---|---|---|---|---|---|
| 合法最小檢驗 Bundle | valid-minimal-lab-bundle.json |
PASSED |
PASSED |
None | None |
| v1.1 新增 UCUM 要求 | observation-quantity-wrong-ucum-system.json |
PASSED |
BLOCKED |
None | LAB-UNIT-002 |
| 缺少內部 Patient reference | missing-internal-reference.json |
BLOCKED |
BLOCKED |
LAB-REF-001, LAB-REF-003 |
LAB-REF-001, LAB-REF-003 |
| 非 Quantity Observation | non-quantity-observation-bundle.json |
PASSED |
PASSED |
None | None |
另外,coversNotApplicableWhenObservationValueIsNotQuantity 會確認非 Quantity Observation 在 v1.1 下:
LAB-UNIT-001 -> NOT_APPLICABLE
LAB-UNIT-002 -> NOT_APPLICABLE
這個情境很重要。
如果只看 Gate,它是 PASSED。
但從規則結果看,它不是 unit 規則通過,而是 unit 規則不適用。
這兩者不能混在一起。
ContractComparisonServiceTests 固定一個最小版本差異:
同一份 observation-quantity-wrong-ucum-system.json
v1.0 -> PASSED
v1.1 -> BLOCKED
測試同時確認:
v1.0 不包含 LAB-UNIT-002
v1.1 包含 LAB-UNIT-002 且結果是 FAIL
這讓版本比較不只是畫面展示。
它有 service-level regression test 保護。
Day 18 之前,首頁已經能顯示很多資訊:
問題是這些區塊比較像陸續加上去的結果。
使用者可以看到很多表,但不一定能立刻理解它們共同構成一份品質測試報告。
Day 19 把結果區統一成:
Quality Test Report
最上方先放:
Overall Quality Gate
Input summary
Layer summary
Layer summary 以驗證層為列,整理:

契約版本比較同一份 Bundle 會用 v1.0 和 v1.1 各跑一次。
如果兩個版本結果相同,畫面顯示:
No impact
如果 v1.1 讓 Gate outcome 改變,或新增 v1.1 才失敗的交換規則,畫面顯示:
Impact
例如 UCUM system/code 在 v1.0 還不檢查,但 v1.1 新增 LAB-UNIT-002 後會出現新的交換規則失敗:
v1.0 -> no LAB-UNIT-002 failure
v1.1 -> LAB-UNIT-002 failed
Failed rule: LAB-UNIT-002
Blocking reason: Exchange contract rule failed.
下面的 Upgrade blocker evidence 會列出可修正的 Expected / Actual 證據。


本機 Maven 測試:
./mvnw test
結果:
Tests run: 60, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

這次測試確認:
BundleParseService 的分層驗證與 Quality Gate 測試維持通過。ContractComparisonService 的 v1.0 / v1.1 差異測試維持通過。ContractScenarioCaseTests 的 4 組代表案例維持通過。Quality Test Report、分層 summary、契約規則證據與版本比較仍會顯示。TW Core validation 不是交換契約規則。
它是 Profile validation layer。
所以應該放在分層驗證覆蓋表,不放進六條規則矩陣。
契約版本比較不是第七條規則。
它是同一份 Bundle 在不同 contract version 下的情境比較。
所以應該放在契約版本情境覆蓋表。
NOT_APPLICABLE 寫成 PASS
非 Quantity Observation 對 unit 規則不是通過。
它是規則不適用。
如果寫成 PASS,會讓讀者誤以為 unit 條件被檢查過。
FAIL 證據。NOT_EVALUATED 來自外部 reference 邊界。NOT_EVALUATED 是設計邊界。Quality Test Report。Quality Test Report、layer labels、scroller 與版本比較斷言。LAB-REF-001 的獨立 NOT_APPLICABLE 小測試。./mvnw test 通過,測試數 60。Day 19 尚未處理:
COMPATIBLE / EXPECTED_BREAKING_CHANGE / UNEXPECTED_REGRESSION 三分類。目前的 MVP 進度:
validation-flow
├─ JSON parse 完成
├─ FHIR R4 parse 完成
├─ FHIR R4 validation 完成
├─ TW Core validation / safe NOT_EVALUATED 完成
├─ Exchange contract rules
│ ├─ LAB-REF-001 完成並覆蓋四態
│ ├─ LAB-REF-002 完成並覆蓋四態
│ ├─ LAB-REF-003 完成並覆蓋四態
│ ├─ LAB-CODE-001 完成,NOT_EVALUATED 不適用於目前設計
│ ├─ LAB-UNIT-001 完成,NOT_EVALUATED 不適用於目前設計
│ └─ LAB-UNIT-002 完成,NOT_EVALUATED 不適用於目前設計
├─ Quality Gate 完成最小版
├─ Contract comparison
│ ├─ ContractVersion 完成最小版
│ ├─ v1.0 / v1.1 rule selection 完成最小版
│ ├─ comparison service test 完成最小版
│ ├─ homepage comparison display 完成最小版
│ └─ upgrade blocker evidence display 完成最小版
├─ Scenario test pack
│ ├─ v1.0 / v1.1 representative cases 完成 4 例
│ └─ NOT_APPLICABLE scenario fixture 完成 1 例
├─ Coverage evidence
│ ├─ six-rule coverage matrix 完成最小版
│ ├─ layer coverage table 完成最小版
│ └─ contract version scenario coverage 完成最小版
├─ Homepage Quality Test Report 完成最小版
└─ Reproducible delivery
├─ Dockerfile 完成最小版
├─ Docker Compose 完成最小版並驗證啟動
└─ GitHub Actions CI 完成最小版
下一步預計處理:
README 支援範圍與限制整理
Repository:twcore-data-quality-gate